iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
AI Engineering

不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天系列 第 21

Day 21 - 一張 12GB 卡怎麼替整套 RAG 編預算:LLM、embedding、reranker 的顯存分帳

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260901/20183550xvNwPlULu6.png
三顆模型的 checkpoint tensor 加起來約 7.85 GiB。卡有 12 GiB,看起來很寬鬆。

再替三個 vLLM instance 各留一份 runtime 峰值,預算就先膨脹到 9.65 GiB——卡的八成,而 KV 還沒進預算。

12 GiB 看似還剩 2.35 GiB,但其中 0.84 GiB 得留給三份落在 util 管理域外的 CUDA context,另外 0.36 GiB 完全不分配給誤差與碎片。剩下的 1.15 GiB 裡,預計分給 cache 的只有 1.03:LLM 0.72、embedding 0.31;reranker 是 encoder,那 0.12 是餘量不是 KV。

昨天那把刀砍的是時間。今天換一種資源:顯存不會因為你聰明就變多,它只能分。

一套 RAG 至少三張嘴

一次 RAG 請求要先把問題變向量(embedding),撈 top-k,重排一次(reranker),最後才輪到 LLM 生成。三個模型、三份權重、三份顯存。

大多數教學只教你把 LLM 伺服起來;embedding 和 reranker 呢?「反正很小,隨便塞。」

於是常見劇本是:vLLM 跑得好好的,加 reranker 那天整台炸掉。因為不設 --gpu-memory-utilization,它就照預設值圈走整張卡的九成——注意是「整張卡」不是「剩下的」。這是 per-instance 的上限,它不管同一張卡上還有沒有別人

⚠️ 那個預設值改過:v0.19.1 以前是 0.9v0.20.0 起是 0.92。別背數字,跑一次 vllm serve --help 看你裝的那版印什麼。

預算表:12 GiB 一毛一毛分

vLLM 分配的方向跟直覺相反。它不是「你要多少 KV 我給你多少」,而是先圈地、再看剩多少:

管理域  = 卡的總顯存 × util
KV 剩額 = 管理域 − 權重 − peak activation − non-Torch − CUDA Graph

**KV 是剩額,不是配額。**扣的不只是 activation:non-Torch(NCCL、attention backend buffer)與 CUDA Graph 預留都算在裡面。啟動時 vLLM 會分列印出來,那份 log 才是你真正的帳。

單位全表統一 GiB——這張卡的「12GB」是 12 GiB = 12,288 MiB。

服務 模型 util 管理域 權重 non-KV runtime = 剩額
LLM Qwen3-8B-AWQ 0.60 7.20 5.68 0.80 0.72(KV,一條 8K FP8)
Embedding Qwen3-Embedding-0.6B 0.16 1.92 1.11 0.50 0.31(KV,1K 約 2.8 條)
Reranker bge-reranker-v2-m3 0.14 1.68 1.06 0.50 0.12(餘量,encoder 無 KV)
預算外:三份 CUDA context 0.07 0.84
未分配:驅動、誤差與碎片 0.03 0.36
合計 1.00 12.00

【權重是 tensor payload(HF ?blobs=true 實抓);剩額由前三欄相減得出】

https://ithelp.ithome.com.tw/upload/images/20260901/20183550BowLFWFjzt.png

圖 1:橘色三段加上預算外的 0.84,合計 2.64 GiB——卡的 22%,裡面沒有一個 byte 是模型權重。

**最後兩列才是這張表能不能用的關鍵。**這是紙上預算:runtime 與 context 兩欄是估算,4070 Ti 實際可見容量也未必剛好 12,288 MiB。把 util 加到 1.00、餘量壓成 0,那不叫預算。所以這裡犧牲「兩條滿額 8K」,換一條 8K 加餘量。

reranker 那格的 0.12 也不是 KV:它是 XLM-R 出身的 cross-encoder,一次前向吐一個分數,不生成、不留 KV,本來就不配 KV block。同樣的減法對 embedding 就不同了——它是 decoder 出身,剩額歸零代表 KV block 數算出 0,vLLM 會在啟動時直接丟 No available memory for the cache blocks。差別不在剩額大小,在剩額歸零的後果。

兩個最容易算錯的地方

一、HF 頁面的檔案大小不是預算數字

https://ithelp.ithome.com.tw/upload/images/20260901/20183550DKH4f1uj0i.png

圖 2:同一顆 reranker,checkpoint 2.12 GiB、FP16 tensor payload 1.06 GiB。

bge-reranker-v2-m3 的 checkpoint 存成 float32,而 vLLM 對 pooling model 會自動降成 fp16(_resolve_auto_dtype())。抄 HF 頁面的大小編預算,這一格會多編一倍。1.06 是 tensor payload,實機載入還要再加上 allocator 對齊與 buffer。

二、KV 精度決定你過不過得了 8K

Qwen3-8B 是 36 層全注意力、8 個 kv_heads、head_dim 128,config 裡沒有 layer_types 也沒有 sliding_window,所以 Day 09 那條 GQA 公式直接套:144 KiB/token @FP16。一條 8K 滿額序列就是 1.125 GiB,而這格只分到 0.72。

https://ithelp.ithome.com.tw/upload/images/20260901/20183550jr7XnpqSBu.png

圖 3:同一格預算,FP16 過 4K、過不了 8K;FP8 過 8K。

所以開 --kv-cache-dtype fp8,每 token 砍成 72 KiB,一條 8K 是 0.5625 GiB,進得去。

代價:官方在幾個 reasoning benchmark 上量到平均差約 1–2 分,長 context 實驗則依模型落在九成多的基準恢復率【vllm-project.github.io/2026/04/22/fp8-kvcache.html】。那不是 Qwen3-8B-AWQ 在你的 RAG 資料上的保證,仍要自己重跑 eval。

⚠️「一條」是吃滿 8K 的最壞情況。Day 14 花了整篇講這件事:實際 RAG 序列短得多,同一份預算裝得下的條數會多得多。

那兩欄不會憑空消失

non-KV runtime 那三格錨在 Day 03 抓過的 activation 峰值 0.5–1 GB,而且 non-Torch 與 CUDA Graph 也擠在同一格裡。

至於 CUDA context:每個碰 CUDA 的 process 驅動都要先建一份,它落在 util 預算之外——vLLM 的記憶體快照是在 device 初始化之後才拍的。所以三個服務的 util 不能加到 1.00。

這兩欄都會因版本、backend 與系統而變,表裡先放一組工作估值,另外保留 0.36 GiB 給誤差與碎片。它們的作用不是保證啟得起來,而是提醒你:權重與 KV 之外,還有一筆不能忽略的 process 成本。

多數「顯存夠不夠」的算法只算權重加 KV,那是單服務的世界。共存時,process 數本身就是成本。

把紙上預算翻成 flag

以下是依預算表換算的起跑參數,不保證每個 vLLM/driver 組合都能原樣啟動vllm serve 是 blocking process,三個 terminal 各跑一條;兩個 pooling 服務的 activation 峰值不只看 max-model-len,也看批次大小,所以一起釘住:

# Terminal 1
CUDA_VISIBLE_DEVICES=0 vllm serve Qwen/Qwen3-8B-AWQ \
  --gpu-memory-utilization 0.60 --max-model-len 8192 \
  --kv-cache-dtype fp8 --port 8000

# Terminal 2
CUDA_VISIBLE_DEVICES=0 vllm serve Qwen/Qwen3-Embedding-0.6B \
  --runner pooling --convert embed \
  --gpu-memory-utilization 0.16 \
  --max-model-len 1024 --max-num-batched-tokens 1024 --port 8001

# Terminal 3
CUDA_VISIBLE_DEVICES=0 vllm serve BAAI/bge-reranker-v2-m3 \
  --runner pooling --gpu-memory-utilization 0.14 \
  --max-model-len 1024 --max-num-batched-tokens 1024 --port 8002

--task 已於 v0.13.0 從 CLI 移除,改用 --runner / --convert。)

96GB 的人怎麼擺

96GB 卡還能用 MIG 把容量與故障域切開,超線只死自己;12GB 卡沒有硬體牆,只能靠每個 instance 的 flag 記帳。

一句話總結階級差:12GB 買到的是「算好帳可以住」,96GB 買到的是「住的時候有牆」。

這份預算的適用範圍

**這組模型以 12 GiB 作為紙上起點。**8 GiB 不是重分 util 能救的:LLM 的 0.60 管理域只有 4.8 GiB,連 5.68 GiB 的權重都裝不下;而三顆模型的 tensor payload 就佔掉 8 GiB 卡的 98%,還沒算任何 runtime 與 context。更小的卡要換模型或減少 process,不是調 util。

我的 T2 是 Windows + WSL 桌機,而這張表的前提是 headless;桌面本身要吃掉 0.3–0.5 GiB,要重現得先扣掉再重算三個 util。

Mac 讀者一樣要編預算,那條線是 Day 07 的 recommendedMaxWorkingSetSize,差別是沒有隔離、也沒得切。

小結

  • KV 是剩額不是配額:vLLM 先用 util × 總顯存 圈地,扣掉權重、activation、non-Torch 與 CUDA Graph,剩下的才給 KV。--gpu-memory-utilization 是 per-instance 上限、互不知情。
  • CUDA context 落在 util 預算之外,所以 util 不能加到 1.00。而這張表的 runtime 與 context 是估算——零餘量的表不叫預算,先以一條 8K 加餘量,作為啟動前的紙上預算。
  • HF 頁面的檔案大小不是預算數字:bge-reranker-v2-m3 的 checkpoint 是 fp32,vLLM 降成 fp16 只剩一半。

明天開第四張地圖。快,救回來了;但快而答錯,等於零。Day 22〈答錯不是模型笨:RAG 準確度的七層歸因〉——一次 RAG 答錯,兇手可能躲在七層裡的任何一層,我們一層一層抓。

咱們明天見。


上一篇
Day 20 - 接受率 1.00,為什麼反而輸給 0.94?投機解碼不是猜準就會快
下一篇
🕵 Day 22 - 答錯不是模型笨:RAG 準確度的七層歸因
系列文
不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言